package com.nulldev.util.graphics.renderIt.renderer.swing.smoothCanvas;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.JPanel;

import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.data.Variables;
import com.nulldev.util.graphics.renderIt.renderer.swing.nitrous.IRenderManager;
import com.nulldev.util.gui.swingAPI.Swing;

public class NitrousSmoothCanvas extends JPanel implements Runnable {
	private static final long FPS_OFFSET = 3L;
	private static final long serialVersionUID = -8581720885946312610L;

	private volatile CanvasFrameCallback callback;

	private boolean aa = false;
	private volatile boolean clearOnFrame;
	private final AtomicReference<RenderMethod> method;
	private final AtomicReference<SyncMethod> sync = new AtomicReference<SyncMethod>(SyncMethod.SLEEP_TO_REFRESH);
	private volatile boolean deferringActive;

	private static final RenderMethod DEFAULT_METHOD = RenderMethod.PAINT;
	private static final long DEFERRED_FRAMERATE = 1000L;

	public NitrousSmoothCanvas() {
		this(DEFAULT_METHOD);
	}

	public NitrousSmoothCanvas(final RenderMethod method) {
		super.setIgnoreRepaint(true);
		super.addComponentListener(new ComponentListener() {

			@Override
			public void componentShown(ComponentEvent e) {
			}

			@Override
			public void componentResized(ComponentEvent e) {
				NitrousSmoothCanvas.this.rrate = Swing.refreshRate() + FPS_OFFSET;
				if (NitrousSmoothCanvas.this.locNitrous != null)
					NitrousSmoothCanvas.this.locNitrous.update();
			}

			@Override
			public void componentMoved(ComponentEvent e) {
			}

			@Override
			public void componentHidden(ComponentEvent e) {
			}
		});
		this.method = new AtomicReference<RenderMethod>(method);
	}

	public RenderMethod getRenderMethod() {
		return this.method.get();
	}

	public SyncMethod getSyncMethod() {
		return this.sync.get();
	}

	public boolean isAA() {
		return this.aa;
	}

	public NitrousSmoothCanvas setAA(final boolean value) {
		this.aa = value;
		return this;
	}

	public NitrousSmoothCanvas setRenderMethod(final RenderMethod method) {
		if (method == null)
			return this;
		this.method.lazySet(method);
		return this;
	}

	public NitrousSmoothCanvas setSyncMethod(final SyncMethod method) {
		if (method == null)
			return this;
		this.sync.lazySet(method);
		return this;
	}

	public String getBackend() {
		if (this.locNitrous == null)
			return "Nitrous";
		return "Nitrous (" + this.locNitrous.getClass().getSimpleName() + ")";
	}

	public String getBackendName() {
		if (this.locNitrous == null)
			return "Nitrous";
		return this.locNitrous.getClass().getSimpleName();
	}

	public NitrousSmoothCanvas setRenderCallback(final CanvasFrameCallback callback) {
		if (callback == null)
			return this;
		this.callback = callback;
		return this;
	}

	public boolean willClearOnFrameStart() {
		return this.clearOnFrame;
	}

	public NitrousSmoothCanvas setClearOnFrameStart(final boolean value) {
		this.clearOnFrame = value;
		return this;
	}

	private final AtomicBoolean stop = new AtomicBoolean(), active = new AtomicBoolean();
	private Thread currThread;
	private IRenderManager locNitrous;
	private volatile long rrate = Swing.refreshRate() + FPS_OFFSET;

	@Override
	public void run() {
		if (this.active.get())
			return;
		else
			this.active.lazySet(true);
		final Toolkit TK = Toolkit.getDefaultToolkit();
		IRenderManager nitrous = null;
		Graphics g2_cached = null;
		try {
			nitrous = this.locNitrous = IRenderManager.tryFor(new ComponentPeerTL(Swing.getPeer(this), this));
			g2_cached = nitrous.getGraphics();
			nitrous.update();
		} catch (Exception e) {
			this.stop.lazySet(true);
			this.active.lazySet(false);
			throw new RuntimeException(e);
		}
		final Graphics g = g2_cached != null ? g2_cached : super.getGraphics();
		if (g != null)
			((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
		this.currThread = Thread.currentThread();
		while (!this.stop.get()) {
			if (deferringActive) {
				try {
					Thread.sleep(DEFERRED_FRAMERATE);
				} catch (InterruptedException e) {
				}
				continue;
			}
			if (this.clearOnFrame)
				g.clearRect(0, 0, getWidth(), getHeight());
			switch (this.method.get()) {
				case PAINT: {
					super.paint(g);
					break;
				}
				case REPAINT: {
					super.repaint();
					break;
				}
				case PAINT_ALL: {
					super.paintAll(g);
					break;
				}
				case UPDATE: {
					super.update(g);
					break;
				}
				case REPAINT_WITHIN_SYNC: {
					super.repaint((1000L / this.rrate));
					break;
				}
				default:
					break;
			}
			switch (this.sync.get()) {
				case SLEEP_TO_REFRESH:
					try {
						Thread.sleep((1000L / this.rrate));
					} catch (InterruptedException e) {
					} // FIXME
					break;
				case VERTICAL_SYNC:
					TK.sync();
					break;
				case NO_SYNC:
				default:
					break;
			}
		}
	}

	public void start() {
		ExecutorManager.get().queueTask(this);
	}

	public void start(final ExecutorService es) {
		Variables.notNullE(es);
		es.execute(this);
	}

	public void start(final IExecutorManager es) {
		Variables.notNullE(es);
		es.queueTask(this);
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		if (this.callback == null)
			return;
		if (this.aa) {
			((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		}
		this.callback.onFrame(g);
	}

	public void signalRepaint() {
		if (this.currThread == null)
			return;
		this.currThread.interrupt();
	}

	public void setDeferred(final boolean value) {
		this.deferringActive = value;
	}

	public boolean isDeferringRendering() {
		return this.deferringActive;
	}
}
